/**************************************************************
*  Apple LLVM version 10.0.0 (clang-1000.10.43.1)             *
*  Copyright (C) 2021 Xuan-Yu-San 307100890@qq.com.           *
*                                                             *
*  This file is part of little-nginx.                         *
*  Little-NGINX is a castration version NGINX for learning    *
*  HTTP Message.                                              *
*                                                             *
*  @file     Server.h                                         *
*  @github   https://gitee.com/xuanyusan/little_nginx.git     *
*  @license  MIT License (MIT)                                *
**************************************************************/

#include <sys/shm.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <cstdlib>
#include <ctime>

#include <set>

#include "Request.hpp"
#include "Response.hpp"
#include "Client.hpp"
#include "Server.hpp"
#include "NginxConfig.hpp"
#include "Logger.hpp"
#include "util.hpp"

using namespace std;

int PORT = 8880;
int HTTP_MAXCONNECT = 1000;
int HTTP_MAXREQUEST = 100;
int HTTP_TIMEOUT = 70;
string config = "";

set<int> childrenPid;

void checkExit(int signal){
    int i;
    for(set<int>::iterator it=childrenPid.begin() ;it!=childrenPid.end();it++) {
        kill(*it, SIGKILL);
    }
    exit(0);
}

void logger(pid_t pid, string notice, string message="") {
#ifdef DEBUG
    cout << "服务进程" << pid << ": " << notice << endl << message << endl;
#else
    istringstream strcin(message);
    string temp;
    getline(strcin, temp);
    cout << "服务进程" << pid << ": " << notice << endl << temp << endl << endl;
#endif
}

vector<NginxConfig> initConfigVector() {
    vector<NginxConfig> res;
    priority_queue<NginxConfig, vector<NginxConfig>, less<NginxConfig> > configQueue;

    stringstream strcin(config);
    string temp;
    strcin >> temp;
    if(temp == "location") {
        configLoop: string location = "";
        string root = "";
        vector<string> index_files;
        index_files.clear();
        pair<string, string> rewrite = make_pair("", "");
        string proxy_pass = "";
        pair<string, string> proxy_redirect = make_pair("", "");
        strcin >> location;
        while(strcin >> temp) {
            if(temp == "root") {
                strcin >> root;
                while(strcin >> temp) {
                    if(temp == "location") goto configLoop;
                    index_files.push_back(temp);
                    configQueue.push(NginxConfig(location, root, index_files, "", make_pair("", ""), make_pair("", "")));
                }
            } else if(temp == "rewrite") {
                strcin >> rewrite.first >> rewrite.second;
                configQueue.push(NginxConfig(location, "", index_files, "", make_pair("", ""), rewrite));
                while(strcin >> temp) {
                    if(temp == "location") goto configLoop;
                }
            } else if(temp == "proxy_pass") {
                strcin >> proxy_pass;
                while(strcin >> temp) {
                    if(temp == "proxy_redirect") {
                        strcin >> proxy_redirect.first >> proxy_redirect.second;
                    }
                    if(temp == "location") {
                        configQueue.push(NginxConfig("/koa", "", index_files, proxy_pass, proxy_redirect, make_pair("", "")));
                        goto configLoop;
                    }
                }
            }
        }
    }
    while(!configQueue.empty()) {
        res.push_back(configQueue.top());
        configQueue.pop();
    }
    return res;
}

int main(int argc, char** argv){
    // 检查LOGGER函数
    #if __cplusplus < 201103L
    cout << "\033[32;5m[LOGGER]\033[0m cpp_version: " << __cplusplus << " If you want to record logger, please use cpp[>=" << "201103" << "] with --std=c++11 option for compile." << endl;
    #endif

    // 解析参数
    ARGS_resolver(argc, argv, PORT, config);

    // 监听端口
    Server server = Server(PORT);
    
    // 代理规则
    vector<NginxConfig> configQueue = initConfigVector();
    for(int i=0;i<configQueue.size();i++){
        Logger_debug(configQueue[i]);
    }

    // 进程控制
    struct sigaction action;
    int status;
    int cnt = 0;
    pid_t pid = fork();

    if(pid){
        // 父进程用于输入相应的控制信号
        // exit ---- 杀死子进程及其所有子进程并退出
        // esc  ---- 强制退出子进程，不对其子进程做处理
        sleep(1);
        while(0 == waitpid(pid,&status,WNOHANG)){
            string control;
            cin >> control;
            if(control == "esc"){
                kill(pid,SIGKILL);
                break;
            } else if(control == "exit"){
                kill(pid,SIGTERM);
                break;
            }
        }
    } else {
        action.sa_handler = checkExit;
        sigemptyset(&action.sa_mask);
        sigaction(SIGTERM, &action, 0);
        // 子进程用于分裂新的进程进行通信，每一次有新的连接就分裂出新的子进程，使服务端可以进行一对多的通信
        loop: Connect connect = server.create_connect();
        pid_t ppid = fork();
        if(ppid){
            if(childrenPid.size() > HTTP_MAXCONNECT) {
                kill(ppid, SIGKILL);
            } else {
                childrenPid.insert(ppid);
            }
            goto loop;
        }
        bool keepConnect = false;
        int timestamp = get_time_stamp();
        int cnt = 0;
        do {
            if(get_time_stamp()-timestamp > HTTP_TIMEOUT) {
                Logger_info("connect", getpid(), ":", "This connection time out.");
                break;
            }
            string client_res = connect.recv_message();
            if(client_res.empty()) continue;
            if(cnt++ > HTTP_MAXREQUEST) {
                Logger_info("connect", getpid(), ":", "This connection reach max request.");
                break;
            }
            Request request = Request(client_res);
            keepConnect = request.get_header_params("Connection") == "keep-alive";
            Logger_info("connect", getpid(), ":", get_time("%E %D %Y %h:%m:%s"), "\033[33;5m\" <<<", connect.getClientIP(), request.getMethod(), request.getPath(), "\"\033[0m");
            // Logger_debug(client_res);
            for(int i=0;i<configQueue.size();i++){
                if(request.match_path(configQueue[i].getLocation())) {
                    if(configQueue[i].check_root()) {
                        string html_path = configQueue[i].getStaticFile(request.getPath());
                        Response response = Response::render_template(html_path);
                        Logger_info("connect", getpid(), ":", get_time("%E %D %Y %h:%m:%s"), "\033[33;5m\" >>>", connect.getClientIP(), response.get_status(), "\"\033[0m");
                        connect.send_message(response.str());
                    } else if(configQueue[i].check_rewrite()) {
                        string url = request.get_request_url();
                        string location = configQueue[i].get_rewrite_url(url);
                        Response response = Response::make_response(302, location);
                        Logger_info("connect", getpid(), ":", get_time("%E %D %Y %h:%m:%s"), "\033[33;5m\" >>>", connect.getClientIP(), response.get_status(), "\"\033[0m");
                        connect.send_message(response.str());
                    } else if(configQueue[i].check_proxy_redirect()) {
                        request.reset_path(configQueue[i].proxy_path);
                        request.update_header_params("Host", concat_host_port(configQueue[i].getProxyHost(), configQueue[i].getProxyPort()));
                        // logger(getpid(), "请求头", request.str());
                        string res = "";
                        try {
                            Logger_info("connect", getpid(), ":", get_time("%E %D %Y %h:%m:%s"), "\033[33;5m\" \\\\\\", request.getMethod(), request.get_request_url(), "\"\033[0m");
                            Client client = Client(configQueue[i].getProxyPort(), configQueue[i].getProxyHost());
                            client.send_message(request.str());
                            res = client.recv_message();
                            Logger_info("connect", getpid(), ":", get_time("%E %D %Y %h:%m:%s"), "\033[33;5m\" ///", request.get_request_url(), Response(res).get_status(), "\"\033[0m");
                        } catch(const exception& e) {
                            Logger_error(e.what());
                        }
                        Response response = res.empty()?Response::make_response(404):Response(res);
                        if(response.get_status() == 302) {
                            response.update_header_params("Location", configQueue[i].getProxyRedirect());
                        }
                        Logger_info("connect", getpid(), ":", get_time("%E %D %Y %h:%m:%s"), "\033[33;5m\" >>>", connect.getClientIP(), response.get_status(), "\"\033[0m");
                        connect.send_message(response.str());
                    }
                    break;
                }
            }
        } while(keepConnect);
        childrenPid.erase(ppid);
    }
    return 0;
}